#pragma once
//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <gmock/gmock.h>
//----------------------------------------------------------------------------------------------------------------------
#include <squirrel.h>
//----------------------------------------------------------------------------------------------------------------------
#include <sqbind/sqbAssert.h>
#include <sqbind/sqbBindMacros.h>
#include <sqbind/sqbClassTypeTag.h>
#include <sqbind/sqbTypeTraits.h>
//----------------------------------------------------------------------------------------------------------------------

#define EXPECT_IS_ALIGNED(_POINTER, _ALIGNMENT) \
  SQBIND_BEGIN_MULTILINE_MACRO(); \
  if ((reinterpret_cast<size_t>(_POINTER) & ((_ALIGNMENT) - 1)) != 0) \
  { \
    ADD_FAILURE() << #_POINTER << " not " << _ALIGNMENT << "-byte aligned. Value of: " << reinterpret_cast<void*>(_POINTER); \
  } \
  SQBIND_END_MULTILINE_MACRO()

//----------------------------------------------------------------------------------------------------------------------
#define _TEST_SQ_SUCCEEDED(_SQ_VM, _SQ_RESULT, _SQ_TEST, _GTEST_FAIL) \
  SQBIND_BEGIN_MULTILINE_MACRO(); \
  if (!_SQ_TEST(_SQ_RESULT)) \
  { \
  sq_getlasterror(_SQ_VM); \
  const SQChar* _SQ_ERROR_STRING = _SC(""); \
  sq_getstring(_SQ_VM, -1, &_SQ_ERROR_STRING); \
  _GTEST_FAIL() << _SQ_ERROR_STRING; \
  } \
  SQBIND_END_MULTILINE_MACRO()

//----------------------------------------------------------------------------------------------------------------------
/// \def EXPECT_SQ_SUCCEEDED
/// \brief
//----------------------------------------------------------------------------------------------------------------------
#define EXPECT_SQ_SUCCEEDED(_SQ_VM, _SQ_RESULT) \
  _TEST_SQ_SUCCEEDED(_SQ_VM, _SQ_RESULT, SQ_SUCCEEDED, ADD_FAILURE)

//----------------------------------------------------------------------------------------------------------------------
/// \def ASSERT_SQ_SUCCEEDED
/// \brief
//----------------------------------------------------------------------------------------------------------------------
#define ASSERT_SQ_SUCCEEDED(_SQ_VM, _SQ_RESULT) \
  _TEST_SQ_SUCCEEDED(_SQ_VM, _SQ_RESULT, SQ_SUCCEEDED, FAIL)

//----------------------------------------------------------------------------------------------------------------------
/// \def EXPECT_SQ_FAILED
/// \brief
//----------------------------------------------------------------------------------------------------------------------
#define EXPECT_SQ_FAILED(_SQ_RESULT) \
  EXPECT_TRUE(SQ_FAILED(_SQ_RESULT))

//----------------------------------------------------------------------------------------------------------------------
/// \def ASSERT_SQ_FAILED
/// \brief
//----------------------------------------------------------------------------------------------------------------------
#define ASSERT_SQ_FAILED(_SQ_RESULT) \
  ASSERT_TRUE(SQ_FAILED(_SQ_RESULT))

//----------------------------------------------------------------------------------------------------------------------
/// \def CLASS_OFFSET
/// \brief
//----------------------------------------------------------------------------------------------------------------------
#define CLASS_OFFSET(BASE_CLASS, DERIVED_CLASS) \
  (reinterpret_cast<char *>(static_cast<BASE_CLASS *>(reinterpret_cast<DERIVED_CLASS *>(0xdeadf00d))) - reinterpret_cast<char *>(0xdeadf00d))

// these are the values that will be pushed on the stack when the appropriate PushObjectOfType is called.
extern const SQInteger      kExpectedInteger;
extern const SQFloat        kExpectedFloat;
extern const SQChar        *kExpectedString;
extern const bool           kExpectedBool;
extern const SQUserPointer  kExpectedUserPointer;
extern const SQUserPointer  kExpectedInstance;

void PushObjectOfType(HSQUIRRELVM vm, SQObjectType type);

// note doesn't include weak refs as they revert to being the original when pushed
const SQObjectType kSquirrelAllTypes[] = {
  OT_NULL,          // 0
  OT_INTEGER,       // 1
  OT_FLOAT,         // 2
  OT_BOOL,          // 3
  OT_STRING,        // 4
  OT_TABLE,         // 5
  OT_ARRAY,         // 6
  OT_USERDATA,      // 7
  OT_CLOSURE,       // 8
  OT_NATIVECLOSURE, // 9
  OT_GENERATOR,     // 10
  OT_USERPOINTER,   // 11
  OT_THREAD,        // 12
  OT_CLASS,         // 13
  OT_INSTANCE,      // 14
};

//----------------------------------------------------------------------------------------------------------------------
/// \brief
//----------------------------------------------------------------------------------------------------------------------
class BoundClass
{
public:
  BoundClass(uint32_t id);
  BoundClass(const BoundClass &rhs);

  const BoundClass &operator = (const BoundClass &rhs);

  bool operator == (const BoundClass &rhs) const;

private:
  uint32_t m_id;
};

SQBIND_DECLARE_SQMALLOC_CLASS(BoundClass);

//----------------------------------------------------------------------------------------------------------------------
/// \brief
//----------------------------------------------------------------------------------------------------------------------
enum TestEnum
{
  kTestEnumValue1,
  kTestEnumValue2,
  kTestEnumValue3,
  kTestEnumCount,
};

SQBIND_DECLARE_ENUM(TestEnum);

//----------------------------------------------------------------------------------------------------------------------
/// \brief
//----------------------------------------------------------------------------------------------------------------------
class OtherBoundClass
{
public:
};

SQBIND_DECLARE_SQMALLOC_CLASS(OtherBoundClass);

//----------------------------------------------------------------------------------------------------------------------
/// \brief
//----------------------------------------------------------------------------------------------------------------------
class UnboundClass
{
};

//----------------------------------------------------------------------------------------------------------------------
/// \brief This class has an invalid specialisation of TypeInfo for test purposes.
//----------------------------------------------------------------------------------------------------------------------
class InvalidTypeInfoClass
{
};

//----------------------------------------------------------------------------------------------------------------------
/// \brief This class has an invalid specialisation of Push, Match and Get for test purposes.
//----------------------------------------------------------------------------------------------------------------------
class InvalidStackUtilsClass
{
};

SQBIND_DECLARE_TYPEINFO(InvalidStackUtilsClass, InvalidStackUtilsClass);

//----------------------------------------------------------------------------------------------------------------------
typedef ::testing::Types<
  char,
  int8_t,
  uint8_t,
  int16_t,
  uint16_t,
  int32_t,
  uint32_t,
#if defined(_SQ64)
  int64_t,
  uint64_t,
#endif // defined(_SQ64)
  TestEnum> IntegerTypes;

//----------------------------------------------------------------------------------------------------------------------
typedef ::testing::Types<
  float,
#if defined(SQUSEDOUBLE)
  double,
#endif // defined(SQUSEDOUBLE)
  ::testing::internal::None> FloatTypes;

//----------------------------------------------------------------------------------------------------------------------
typedef ::testing::Types<
  const SQChar *,
  SQFloat,
  SQInteger,
  SQBool,
  SQUserPointer> NativeTypes;

//----------------------------------------------------------------------------------------------------------------------
typedef ::testing::Types<
  bool,
  char,
  int8_t,
  uint8_t,
  int16_t,
  uint16_t,
  int32_t,
  uint32_t,
  float,
  const SQChar *,
  SQUserPointer,
  BoundClass,
  TestEnum,
#if defined(_SQ64)
  int64_t,
  uint64_t,
#endif // defined(_SQ64)
#if defined(SQUSEDOUBLE)
  double,
#endif // defined(SQUSEDOUBLE)
  ::testing::internal::None> AllTypes;

#include "TypeHelpers.inl"
